///////////////////////////////////////////////////////////////////////////////
//
// Project:  C++ Test Suite for GDAL/OGR
// Purpose:  Main program of C++ Unit Tests runner for GDAL
// Author:   Mateusz Loskot <mateusz@loskot.net>
//
///////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2006, Mateusz Loskot <mateusz@loskot.net>
/*
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#ifdef _MSC_VER
#define WIN32_LEAN_AND_MEAN
#endif // _MSC_VER

#include "gdal_unit_test.h"

#include "cpl_conv.h"
#include "cpl_multiproc.h"
#include "gdal.h"
#include "ogr_api.h"
#include "ogrsf_frmts.h"
#include "test_data.h"

#include <algorithm>
#include <iostream>
#include <string>

#include "gtest_include.h"

namespace tut
{
    // Common test data path
    std::string const common::data_basedir(TUT_ROOT_DATA_DIR);
    std::string const common::tmp_basedir(TUT_ROOT_TMP_DIR);

    ::testing::AssertionResult CheckEqualGeometries(OGRGeometryH lhs, OGRGeometryH rhs, double tolerance)
    {
        // Test raw pointers
        if(nullptr == lhs)
        {
            return ::testing::AssertionFailure() << "lhs is null";
        }
        if(nullptr == rhs)
        {
            return ::testing::AssertionFailure() << "rhs is null";
        }

        // Test basic properties
        if( strcmp(OGR_G_GetGeometryName(lhs), OGR_G_GetGeometryName(rhs)) != 0 )
        {
            return ::testing::AssertionFailure() << "OGR_G_GetGeometryName(lhs) = " << OGR_G_GetGeometryName(lhs) << ". OGR_G_GetGeometryName(rhs) = " << OGR_G_GetGeometryName(rhs);
        }

        if( OGR_G_GetGeometryCount(lhs) != OGR_G_GetGeometryCount(rhs) )
        {
            return ::testing::AssertionFailure() << "OGR_G_GetGeometryCount(lhs) = " << OGR_G_GetGeometryCount(lhs) << ". OGR_G_GetGeometryCount(rhs) = " << OGR_G_GetGeometryCount(rhs);
        }

        if( OGR_G_GetPointCount(lhs) != OGR_G_GetPointCount(rhs) )
        {
            return ::testing::AssertionFailure() << "OGR_G_GetPointCount(lhs) = " << OGR_G_GetPointCount(lhs) << ". OGR_G_GetPointCount(rhs) = " << OGR_G_GetPointCount(rhs);
        }

        if (OGR_G_GetGeometryCount(lhs) > 0)
        {
            // Test sub-geometries recursively
            const int count = OGR_G_GetGeometryCount(lhs);
            for (int i = 0; i < count; ++i)
            {
                auto res = CheckEqualGeometries(OGR_G_GetGeometryRef(lhs, i),
                                            OGR_G_GetGeometryRef(rhs, i),
                                            tolerance);
                if( !res )
                    return res;
            }
        }
        else
        {
            std::unique_ptr<OGRGeometry> lhs_normalized_cpp;
            std::unique_ptr<OGRGeometry> rhs_normalized_cpp;
            if( EQUAL(OGR_G_GetGeometryName(lhs), "LINEARRING") )
            {
                // Normalize() doesn't work with LinearRing
                OGRLineString lhs_as_ls(*OGRGeometry::FromHandle(lhs)->toLineString());
                lhs_normalized_cpp.reset(lhs_as_ls.Normalize());
                OGRLineString rhs_as_ls(*OGRGeometry::FromHandle(rhs)->toLineString());
                rhs_normalized_cpp.reset(rhs_as_ls.Normalize());
            }
            else
            {
                lhs_normalized_cpp.reset(OGRGeometry::FromHandle(OGR_G_Normalize(lhs)));
                rhs_normalized_cpp.reset(OGRGeometry::FromHandle(OGR_G_Normalize(rhs)));
            }
            auto lhs_normalized = OGRGeometry::ToHandle(lhs_normalized_cpp.get());
            auto rhs_normalized = OGRGeometry::ToHandle(rhs_normalized_cpp.get());

            // Test geometry points
            const std::size_t csize = 3;
            double a[csize] = { 0 };
            double b[csize] = { 0 };
            double d[csize] = { 0 };
            double dmax = 0;

            const int count = OGR_G_GetPointCount(lhs_normalized);
            for (int i = 0; i < count; ++i)
            {
                OGR_G_GetPoint(lhs_normalized, i, &a[0], &a[1], &a[2]);
                OGR_G_GetPoint(rhs_normalized, i, &b[0], &b[1], &b[2]);

                // Test vertices
                for (std::size_t c = 0; c < csize; ++c)
                {
                    d[c] = std::fabs(a[c] - b[c]);
                }

                const double* pos = std::max_element(d, d + csize);
                dmax = *pos;

                if( dmax > tolerance )
                {
                    return ::testing::AssertionFailure() <<
                        "dmax = " << dmax << " is > tolerance = " << tolerance << " on vertex " << i;
                }
            }
        }

        return ::testing::AssertionSuccess();
    }

} // namespace tut

int main(int argc, char* argv[])
{
    // Register GDAL/OGR drivers
    ::GDALAllRegister();
    ::OGRRegisterAll();

    std::cout
        << "GDAL C/C++ API tests"
        << " (" << ::GDALVersionInfo("--version") << ")"
        << "\n---------------------------------------------------------\n";

    argc = GDALGeneralCmdLineProcessor( argc, &argv, 0 );

    testing::InitGoogleTest(&argc, argv);

    int nRetCode = RUN_ALL_TESTS();

    CSLDestroy(argv);
    GDALDestroyDriverManager();

    GDALAllRegister();
    GDALDestroyDriverManager();

    OGRCleanupAll();

    CPLDumpSharedList( nullptr );
    CPLCleanupTLS();

    return nRetCode;
}
